﻿namespace MvvmFx.CaliburnMicro
{
    using System;
    using System.Linq;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Drawing;
#if WEBGUI
    using Gizmox.WebGUI.Forms;
#else
    using System.Windows.Forms;
#endif

    /// <summary>
    /// A service that manages windows.
    /// </summary>
    public interface IWindowManager
    {
        /// <summary>
        /// Shows a modal dialog Form for the specified model.
        /// </summary>
        /// <param name="rootModel">The root model.</param>
        /// <param name="context">The context.</param>
        /// <param name="settings">The optional dialog settings.</param>
        /// <returns>The return value should return a value denoting whether the dialog result was afirmative.
        /// Strange enough, it always returns null!!!</returns>
        bool? ShowDialog(object rootModel, object context = null, IDictionary<string, object> settings = null);

        /// <summary>
        /// Shows a Form for the specified model.
        /// </summary>
        /// <param name="rootModel">The root model.</param>
        /// <param name="context">The context.</param>
        /// <param name="settings">The settings.</param>
        void ShowWindow(object rootModel, object context = null, IDictionary<string, object> settings = null);

        /// <summary>
        /// Shows a Form for the specified model, and that form is supposed to be the main application form.
        /// </summary>
        /// <param name="rootModel">The root model.</param>
        /// <param name="context">The context.</param>
        /// <param name="settings">The settings.</param>
        void ShowMainWindow(object rootModel, object context = null, IDictionary<string, object> settings = null);
    }

    /// <summary>
    /// A service that manages windows.
    /// </summary>
    public class WindowManager : IWindowManager
    {
        /// <summary>
        /// Shows a modal dialog Form for the specified model.
        /// </summary>
        /// <param name="rootModel">The root model.</param>
        /// <param name="context">The context.</param>
        /// <param name="settings">The optional dialog settings.</param>
        /// <returns>
        /// The return value should return a value denoting whether the dialog result was afirmative.
        /// Strange enough, it always returns null!!!
        /// </returns>
        public virtual bool? ShowDialog(object rootModel, object context = null,
            IDictionary<string, object> settings = null)
        {
            DialogResult result = DialogResult.OK;

            Execute.OnUIThread(() =>
            {
                using (var dlg = CreateWindow(rootModel, true, context, settings))
                {
                    dlg.ShowDialog();
                    result = dlg.DialogResult;
                }
            });

            if (result == DialogResult.OK || result == DialogResult.Yes)
                return true;
            if (result == DialogResult.Cancel || result == DialogResult.No)
                return true;
            return null;
        }

        /// <summary>
        /// Shows a Form for the specified model.
        /// </summary>
        /// <param name="rootModel">The root model.</param>
        /// <param name="context">The context.</param>
        /// <param name="settings">The settings.</param>
        public virtual void ShowWindow(object rootModel, object context = null,
            IDictionary<string, object> settings = null)
        {
            Form navWindow = null;

            if (null != ApplicationContext.MainWindow)
                navWindow = ApplicationContext.MainWindow;

            if (null != navWindow)
            {
                //var window = CreatePage(rootModel, context, settings);
                //navWindow.Navigate(window);
            }
            else
            {
                var window = CreateWindow(rootModel, true, context, settings);

                if (FormStartPosition.CenterParent == window.StartPosition && null != window.Owner)
                {
                    window.StartPosition = FormStartPosition.Manual;
                    window.Location =
                        new Point(
                            window.Owner.Location.X + (window.Owner.Width - window.Width)/2,
                            window.Owner.Location.Y + (window.Owner.Height - window.Height)/2);
                }

                Execute.OnUIThread(() => { window.Show(); });
            }
        }

        /// <summary>
        /// For the specified model, shows a Form that is supposed to be the main application form.
        /// </summary>
        /// <param name="rootModel">The root model.</param>
        /// <param name="context">The context.</param>
        /// <param name="settings">The settings.</param>
        public virtual void ShowMainWindow(object rootModel, object context = null,
            IDictionary<string, object> settings = null)
        {
            if (ApplicationContext.StartupForm != null)
            {
                Form root = CreateWindow(rootModel, true, context, settings, ApplicationContext.StartupForm);
                new ApplicationContext(root);
            }
#if WINFORMS
            else
            {
                Form root = CreateWindow(rootModel, true, context, settings);
                var applicationContext = new ApplicationContext(root);
                Execute.OnUIThread(() =>
                {
                    applicationContext.MainForm.ShowDialog();
                    //Application.Run(applicationContext);
                });
            }
#endif
        }

        /// <summary>
        /// Creates the window.
        /// </summary>
        /// <param name="rootModel">The root model.</param>
        /// <param name="isDialog">if set to <c>true</c> the Form is a dialog form.</param>
        /// <param name="context">The context.</param>
        /// <param name="settings">The optional popup settings.</param>
        /// <param name="rootForm">The root form.</param>
        /// <returns>
        /// The Form.
        /// </returns>
        /// <remarks>
        /// The calling method only passes the root form parameter in ShowMainWindow, when the main form already exists.
        /// This is necessary for Visual WebGUI (or Windows if the Bootstrapper is onvoked after the main form creation).
        /// </remarks>
        protected virtual Form CreateWindow(object rootModel, bool isDialog, object context,
            IDictionary<string, object> settings, Form rootForm = null)
        {
            Form ensuredView;
            if (rootForm != null)
                ensuredView = rootForm;
            else
                ensuredView = EnsureForm(rootModel, ViewLocator.LocateForModel(rootModel, null, null), isDialog);

            ViewModelBinder.Bind(rootModel, ensuredView, null);

            var haveDisplayName = rootModel as IHaveDisplayName;
            if (null != haveDisplayName)
            {
                ensuredView.DataBindings.Add(new Binding("Text", rootModel, "DisplayName", true,
                    DataSourceUpdateMode.OnPropertyChanged));
            }

            ApplySettings(ensuredView, settings);

            new WindowConductor(rootModel, ensuredView);

            return ensuredView;
        }

        private bool ApplySettings(object target, IEnumerable<KeyValuePair<string, object>> settings)
        {
            if (null != settings)
            {
                var type = target.GetType();

                foreach (var pair in settings)
                {
                    var propertyInfo = type.GetProperty(pair.Key);
                    if (null != propertyInfo)
                        propertyInfo.SetValue(target, pair.Value, null);
                }

                return true;
            }

            return false;
        }

        /// <summary>
        /// Ensures that the view is a <see cref="Form" /> or is wrapped by one.
        /// </summary>
        /// <param name="model">The view model.</param>
        /// <param name="view">The view.</param>
        /// <param name="isDialog">if set to <c>true</c> is a dialog.</param>
        /// <returns>
        /// The original Form or a new Form embedding the "view" control.
        /// </returns>
        protected virtual Form EnsureForm(object model, object view, bool isDialog)
        {
            var form = view as Form;

            if (null == form)
            {
                form = new Form();

                var contentControl = new ContentContainer()
                {
                    Dock = DockStyle.Fill,
                    Location = new Point(0, 0),
                    TabIndex = 0,
                    Content = model
                };

                form.Controls.Add(contentControl);

#if WEBGUI
                var owner = ApplicationContext.WebGUIActiveForm;
#else
                var owner = Form.ActiveForm;
#endif
                if (null != owner && form != owner)
                {
                    form.StartPosition = FormStartPosition.CenterParent;
                    form.Owner = owner;
                }
                else
                {
                    form.StartPosition = FormStartPosition.CenterScreen;
                }
            }
            else
            {
                if (isDialog)
                {
                    var owner = InferOwnerOf(form);
                    if (owner != null)
                        form.Owner = owner;
                    else
                        form.StartPosition = FormStartPosition.CenterScreen;
                }
            }

            return form;
        }

        /// <summary>
        /// Infers the Form owner for a given Form.
        /// </summary>
        /// <param name="form">The form to whose owner needs to be determined.</param>
        /// <returns>The Form's owner</returns>
        protected virtual Form InferOwnerOf(Form form)
        {
            if (ApplicationContext.MainWindow == null)
            {
                return null;
            }

            var active = Application.OpenForms.Cast<Form>().FirstOrDefault();
            active = active ?? ApplicationContext.MainWindow;
            return active == form ? null : active;
        }
    }

    internal class WindowConductor
    {
        private bool deactivatingFromView;
        private bool deactivateFromViewModel;
        private bool actuallyClosing;
        private readonly Form view;
        private readonly object model;

        public WindowConductor(object model, Form view)
        {
            this.model = model;
            this.view = view;

            var activatable = model as IActivate;
            if (null != activatable)
                activatable.Activate();

            var deactivatable = model as IDeactivate;
            if (null != deactivatable)
            {
                view.Closed += Closed;
                deactivatable.Deactivated += Deactivated;
            }

            var guard = model as IGuardClose;
            if (null != guard)
                view.Closing += Closing;
        }

        private void Closed(object sender, EventArgs e)
        {
            view.Closed -= Closed;
            view.Closing -= Closing;

            if (deactivateFromViewModel)
                return;

            var deactivatable = (IDeactivate) model;

            deactivatingFromView = true;
            deactivatable.Deactivate(true);
            deactivatingFromView = false;
        }

        private void Deactivated(object sender, DeactivationEventArgs e)
        {
            if (!e.WasClosed)
                return;

            ((IDeactivate) model).Deactivated -= Deactivated;

            if (deactivatingFromView)
                return;

            deactivateFromViewModel = true;
            actuallyClosing = true;
            view.Close();
            actuallyClosing = false;
            deactivateFromViewModel = false;
        }

        private void Closing(object sender, CancelEventArgs e)
        {
            if (e.Cancel)
                return;

            var guard = (IGuardClose) model;

            if (actuallyClosing)
            {
                actuallyClosing = false;
                return;
            }

            bool runningAsync = false;
            bool shouldEnd = false;

            guard.CanClose(canClose =>
            {
                Execute.OnUIThread(() =>
                {
                    if (runningAsync && canClose)
                    {
                        actuallyClosing = true;
                        view.Close();
                    }
                    else e.Cancel = !canClose;

                    shouldEnd = true;
                });
            });

            if (shouldEnd)
                return;

            runningAsync = e.Cancel = true;
        }
    }
}